/* SPDX-License-Identifier: GPL-2.0 */
#ifndef UTILS_TIME_H
#define UTILS_TIME_H

#include <time.h>
#include <sys/time.h>
#include <utils/types.h>
#include <utils/math64.h>

/* Parameters used to convert the timespec values: */
#define MSEC_PER_SEC	1000L
#define USEC_PER_MSEC	1000L
#define NSEC_PER_USEC	1000L
#define NSEC_PER_MSEC	1000000L
#define USEC_PER_SEC	1000000L
#define NSEC_PER_SEC	1000000000L
#define FSEC_PER_SEC	1000000000000000LL

typedef __s64 time64_t;
typedef __u64 timeu64_t;

#define TIME64_MAX			((s64)~((u64)1 << 63))
#define TIME64_SEC_MAX      (TIME64_MAX / NSEC_PER_SEC)

struct timespec64 {
    time64_t	tv_sec;			/* seconds */
    s64		    tv_nsec;		/* nanoseconds */
};

static inline int timespec64_equal(const struct timespec64 *a,
                   const struct timespec64 *b)
{
    return (a->tv_sec == b->tv_sec) && (a->tv_nsec == b->tv_nsec);
}

/*
 * lhs < rhs:  return <0
 * lhs == rhs: return 0
 * lhs > rhs:  return >0
 */
static inline int timespec64_compare(const struct timespec64 *lhs, const struct timespec64 *rhs)
{
    if (lhs->tv_sec < rhs->tv_sec)
        return -1;
    if (lhs->tv_sec > rhs->tv_sec)
        return 1;
    return lhs->tv_nsec - rhs->tv_nsec;
}

extern void set_normalized_timespec64(struct timespec64 *ts, time64_t sec, s64 nsec);

/*
 * timespec64_add_safe assumes both values are positive and checks for
 * overflow. It will return TIME64_MAX in case of overflow.
 */
extern struct timespec64 timespec64_add_safe(const struct timespec64 lhs,
                     const struct timespec64 rhs);

static inline struct timespec64 timespec64_add(struct timespec64 lhs,
                        struct timespec64 rhs)
{
    struct timespec64 ts_delta;
    set_normalized_timespec64(&ts_delta, lhs.tv_sec + rhs.tv_sec,
                lhs.tv_nsec + rhs.tv_nsec);
    return ts_delta;
}

/*
 * sub = lhs - rhs, in normalized form
 */
static inline struct timespec64 timespec64_sub(struct timespec64 lhs,
                        struct timespec64 rhs)
{
    struct timespec64 ts_delta;
    set_normalized_timespec64(&ts_delta, lhs.tv_sec - rhs.tv_sec,
                lhs.tv_nsec - rhs.tv_nsec);
    return ts_delta;
}

/*
 * Returns true if the timespec64 is norm, false if denorm:
 */
static inline bool timespec64_valid(const struct timespec64 *ts)
{
    /* Dates before 1970 are bogus */
    if (ts->tv_sec < 0)
        return false;
    /* Can't have more nanoseconds then a second */
    if ((u64)ts->tv_nsec >= NSEC_PER_SEC)
        return false;
    return true;
}

static inline bool timespec64_valid_strict(const struct timespec64 *ts)
{
    if (!timespec64_valid(ts))
        return false;
    /* Disallow values that could overflow ktime_t */
    if ((u64)ts->tv_sec >= TIME64_SEC_MAX)
        return false;
    return true;
}

/**
 * timespec64_to_ns - Convert timespec64 to nanoseconds
 * @ts:		pointer to the timespec64 variable to be converted
 *
 * Returns the scalar nanosecond representation of the timespec64
 * parameter.
 */
static inline s64 timespec64_to_ns(const struct timespec64 *ts)
{
    return ((s64) ts->tv_sec * NSEC_PER_SEC) + ts->tv_nsec;
}

/**
 * timespec64_add_ns - Adds nanoseconds to a timespec64
 * @a:		pointer to timespec64 to be incremented
 * @ns:		unsigned nanoseconds value to be added
 *
 * This must always be inlined because its used from the x86-64 vdso,
 * which cannot call other kernel functions.
 */
static __always_inline void timespec64_add_ns(struct timespec64 *a, u64 ns)
{
    a->tv_sec += __iter_div_u64_rem(a->tv_nsec + ns, NSEC_PER_SEC, &ns);
    a->tv_nsec = ns;
}

#if __BITS_PER_LONG == 64

/* timespec64 is defined as timespec here */
static inline struct timespec timespec64_to_timespec(const struct timespec64 ts64)
{
    return *(const struct timespec *)&ts64;
}

static inline struct timespec64 timespec_to_timespec64(const struct timespec ts)
{
    return *(const struct timespec64 *)&ts;
}

#else
static inline struct timespec timespec64_to_timespec(const struct timespec64 ts64)
{
    struct timespec ret;

    ret.tv_sec = (time_t)ts64.tv_sec;
    ret.tv_nsec = ts64.tv_nsec;
    return ret;
}

static inline struct timespec64 timespec_to_timespec64(const struct timespec ts)
{
    struct timespec64 ret;

    ret.tv_sec = ts.tv_sec;
    ret.tv_nsec = ts.tv_nsec;
    return ret;
}
#endif

static inline int timespec_equal(const struct timespec *a,
                 const struct timespec *b)
{
    return (a->tv_sec == b->tv_sec) && (a->tv_nsec == b->tv_nsec);
}

/*
 * lhs < rhs:  return <0
 * lhs == rhs: return 0
 * lhs > rhs:  return >0
 */
static inline int timespec_compare(const struct timespec *lhs, const struct timespec *rhs)
{
    if (lhs->tv_sec < rhs->tv_sec)
        return -1;
    if (lhs->tv_sec > rhs->tv_sec)
        return 1;
    return lhs->tv_nsec - rhs->tv_nsec;
}

/*
 * Returns true if the timespec is norm, false if denorm:
 */
static inline bool timespec_valid(const struct timespec *ts)
{
    /* Dates before 1970 are bogus */
    if (ts->tv_sec < 0)
        return false;
    /* Can't have more nanoseconds then a second */
    if ((u64)ts->tv_nsec >= NSEC_PER_SEC)
        return false;
    return true;
}

/**
 * timespec_to_ns - Convert timespec to nanoseconds
 * @ts:		pointer to the timespec variable to be converted
 *
 * Returns the scalar nanosecond representation of the timespec
 * parameter.
 */
static inline s64 timespec_to_ns(const struct timespec *ts)
{
    return ((s64) ts->tv_sec * NSEC_PER_SEC) + ts->tv_nsec;
}

/**
 * timespec_add_ns - Adds nanoseconds to a timespec
 * @a:		pointer to timespec to be incremented
 * @ns:		unsigned nanoseconds value to be added
 *
 * This must always be inlined because its used from the x86-64 vdso,
 * which cannot call other kernel functions.
 */
static __always_inline void timespec_add_ns(struct timespec *a, u64 ns)
{
    a->tv_sec += __iter_div_u64_rem(a->tv_nsec + ns, NSEC_PER_SEC, &ns);
    a->tv_nsec = ns;
}

/**
 * timeval_to_ns - Convert timeval to nanoseconds
 * @ts:		pointer to the timeval variable to be converted
 *
 * Returns the scalar nanosecond representation of the timeval
 * parameter.
 */
static inline s64 timeval_to_ns(const struct timeval *tv)
{
    return ((s64) tv->tv_sec * NSEC_PER_SEC) +
        tv->tv_usec * NSEC_PER_USEC;
}

static inline bool timeval_valid(const struct timeval *tv)
{
    /* Dates before 1970 are bogus */
    if (tv->tv_sec < 0)
        return false;

    /* Can't have more microseconds then a second */
    if (tv->tv_usec < 0 || tv->tv_usec >= USEC_PER_SEC)
        return false;

    return true;
}

struct itimerspec64 {
    struct timespec64 it_interval;
    struct timespec64 it_value;
};

static inline bool itimerspec64_valid(const struct itimerspec64 *its)
{
    if (!timespec64_valid(&(its->it_interval)) ||
        !timespec64_valid(&(its->it_value)))
        return false;

    return true;
}

/**
 * ns_to_timespec - Convert nanoseconds to timespec
 * @nsec:	the nanoseconds value to be converted
 *
 * Returns the timespec representation of the nsec parameter.
 */
extern struct timespec ns_to_timespec(const s64 nsec);

/**
 * ns_to_timespec64 - Convert nanoseconds to timespec64
 * @nsec:	the nanoseconds value to be converted
 *
 * Returns the timespec64 representation of the nsec parameter.
 */
extern struct timespec64 ns_to_timespec64(const s64 nsec);

/**
 * ns_to_timeval - Convert nanoseconds to timeval
 * @nsec:	the nanoseconds value to be converted
 *
 * Returns the timeval representation of the nsec parameter.
 */
extern struct timeval ns_to_timeval(const s64 nsec);

time64_t mktime64(const unsigned int year, const unsigned int mon,
            const unsigned int day, const unsigned int hour,
            const unsigned int min, const unsigned int sec);

void time64_to_tm(time64_t totalsecs, int offset, struct tm *result);

/*
 *	These inlines deal with timer wrapping correctly. You are
 *	strongly encouraged to use them
 *	1. Because people otherwise forget
 *	2. Because if the timer wrap changes in future you won't have to
 *	   alter your driver code.
 *
 * time_after(a,b) returns true if the time a is after time b.
 *
 * Do this with "<0" and ">=0" to only test the sign of the result. A
 * good compiler would generate better code (and a really good compiler
 * wouldn't care). Gcc is currently neither.
 */
#define time_after(a,b)		\
    (typecheck(unsigned long, a) && \
     typecheck(unsigned long, b) && \
     ((long)((b) - (a)) < 0))
#define time_before(a,b)	time_after(b,a)

#define time_after_eq(a,b)	\
    (typecheck(unsigned long, a) && \
     typecheck(unsigned long, b) && \
     ((long)((a) - (b)) >= 0))
#define time_before_eq(a,b)	time_after_eq(b,a)

/*
 * Calculate whether a is in the range of [b, c].
 */
#define time_in_range(a,b,c) \
    (time_after_eq(a,b) && \
     time_before_eq(a,c))

/*
 * Calculate whether a is in the range of [b, c).
 */
#define time_in_range_open(a,b,c) \
    (time_after_eq(a,b) && \
     time_before(a,c))

/* Same as above, but does so with platform independent 64bit types.
 * These must be used when utilizing jiffies (i.e. return value of
 * get_jiffies() */
#define time_after64(a,b)	\
    (typecheck(__u64, a) &&	\
     typecheck(__u64, b) && \
     ((__s64)((b) - (a)) < 0))
#define time_before64(a,b)	time_after64(b,a)

#define time_after_eq64(a,b)	\
    (typecheck(__u64, a) && \
     typecheck(__u64, b) && \
     ((__s64)((a) - (b)) >= 0))
#define time_before_eq64(a,b)	time_after_eq64(b,a)

#define time_in_range64(a, b, c) \
    (time_after_eq64(a, b) && \
     time_before_eq64(a, c))

#endif /* !UTILS_TIME_H */
